"""
VCS base module
===============

To add an Adaptor, insert a line into the
dictionary TOOLS below: 
		<name_of_adaptor> : <name_of_module>

For debugging reasons, environment variable VCSDEBUG
can be set to a non-empty value. The effect will be a trace output
of the adaptor commands.
VCSDEBUG = 1		print to stdout (this will usually
					appear in the ErrorLog window of Sniff)
VCSDEBUG = tmp		output is redirected to the file /tmp/vcs_trace


VCS Variable definitions:
=========================
Variable	Python Name		Format/Type
---------------------------------------------------------------
%A		repositoryFile	abs. filename/list of filenames
%B		branchName		String
%b		branchFlag		Boolean (1/0)
%C		comment			String
%D		date			YYYY/MM/DD HH:MM:SS
%F		workingFile		working file name without path
%f		catDir			file type subdirectory
%H		SSWE			shared source WE roots (semicolon separated)
%G		SSWS			shared source workspaces (semicolon sparated)
%g		SOWS			shared object workspaces (semicolon separated)
%I		description		String
%i		pid				Integer
%L		lockType		N/E/C  (None/Excl./Conc.)
%N		projectName		String
%P		repositoryDir	path (with adapter-specific tool subdir. and file type dir.)
%p		RCCSPath		path (without ...)
%R		repositoryRoot	abs. path
%r		projectDir		abs. or rel. path
%S		symbolName		String
%s		forceFlag		Boolean
%T		tmpFile			abs. filename
%t		tmpDir			abs. path
%U		user			String
%V		revision		String
%v		revision2		String (for second file)
%W		workingDir		abs. path
%w		privateRoot		abs. path
%c		defaultConfig	Default Configuration of the WE
%M		platform		Platform as defined in the WE
%E		workingEnvName	Name of the WE
%x		remoteWSRoot	Remote workspace root
%d		sniffDir
%e		OSTempDir
---------------------------------------------------------------
"""

import sys, os, string, chmod, time
from stat import *
try:
	from etgui import RunCmd
except ImportError:
	def RunCmd(*args):
		print "RunCmd",args
		return 0, '', ''
_chmod = chmod.chmod

# --- constants  --------------------------------------------------

_variables=(
	('A', 'repositoryFile', 'abs. filename /'),
	('B', 'branchName',     'String'),
	('b', 'branchFlag',     'Boolean (1/0)'),
	('C', 'comment',        'String'),
	('D', 'date',           'YYYY/MM/DD HH:MM:SS'),
	('F', 'workingFile',    'working file name without path'),
	('f', 'catDir',         'file type subdirectory'),
	('H', 'SSWE',           'shared source WE roots (semicolon separated)'),
	('G', 'SSWS',           'shared source workspaces (semicolon sparated)'),
	('g', 'SOWS',           'shared object workspaces (semicolon separated)'),
	('I', 'description',    'String'),
	('i', 'pid',            'Integer'),
	('L', 'lockType',       'N/E/C  (None/Excl./Conc.)'),
	('N', 'projectName',    'String'),
	('P', 'repositoryDir',  'path (with adapter-specific tool subdir. and file type dir.)'),
	('p', 'RCCSPath',       'path (without ...)'),
	('R', 'repositoryRoot', 'abs. path'),
	('r', 'projectDir',     'abs. or rel. path'),
	('S', 'symbolName',     'String'),
	('s', 'forceFlag',      'Boolean'),
	('T', 'tmpFile',        'abs. filename'),
	('t', 'tmpDir',         'abs. path'),
	('U', 'user',           'String'),
	('V', 'revision',       'String'),
	('v', 'revision2',      'String (for second file)'),
	('W', 'workingDir',     'abs. path'),
	('w', 'privateRoot',    'abs. path'),
	('c', 'defaultConfig',  'Default Configuration of the WE'),
	('M', 'platform',       'Platform as defined in the WE'),
	('E', 'workingEnvName', 'Name of the WE'),
	('x', 'remoteWSRoot',   'Remote workspace root'),
	('d', 'sniffDir',       ''),
	('e', 'OSTempDir',      ''),
)
_commands=(
	'CheckOut',
	'CheckOutProject',
	'CreateRepositoryFile',
	'CheckIn',
	'Lock',
	'SetWorkFileReadOnly',
	'UpdateProject',
	'UpdateFile',
	'Unlock',
	'BreakLock',
	'DeleteVersion',
	'SetDescription',
	'SetSymbol',
	'DeleteSymbol',
	'ChangeComment',
	'Description',
	'History',
	'Head',
	'SymbolList',
	'RevisionOfSymbol',
	'HistoryOfRevision',
	'Comment',
	'Lockers',
	'IsLockedBy',
	'IsLocked',
	'RepositoryDir',
	'RepositoryFile',
	'TempDir',
	'TempFile',
	'BranchDef',
	'RepositoryCheckDef',
	'IsDifferent',
	'DiffText_W_V',
	'DiffText_V_V',
	'CheckRepositoryDir',
	'CheckRepositoryFile',
	'DateOfRepositoryFile',
	'CreateRepositoryDir',
	'TestTool',
	'Constructor',
	'Destructor'
)

#               tool name	:   module name
TOOLS	=   {	'RCS_CROSS'	:   'RCS_CROSS',
				'RCS'		:   'RCS',
				'RCS_Py'	:   'RCS',
				'CVS'           :   'CVS',
				'CVS_Py'        :   'CVS',
				'PVCS'		:   'PVCS',
				'PVCS_Py'	:   'PVCS',
				'ClearCase'	:   'ClearCase',
				'ClearCase_Py'	:   'ClearCase',
				'Continuus'	:   'Continuus',
				'Perforce'	:   'Perforce',
				'TeamConnection':   'TeamConnection'
			}

TRUE	= 1
FALSE	= 0
NL	= '\n'

SLASH2BACKSLASH = string.maketrans('/', '\\')
BACKSLASH2SLASH = string.maketrans('\\', '/')

try:
	PATH_LIST = string.split(os.environ['Path'], os.pathsep)
except:
	try:
		PATH_LIST = string.split(os.environ['PATH'], os.pathsep)
	except:
		PATH_LIST = []

PID = os.getpid()
DELIMITER = ';'

UNIX 	= 'Unix'
WINDOWS = 'Windows'

# --- system dependencies ----------------------------------------------

def unixpath(path):
	if not path: return ''
	path = string.translate(path, BACKSLASH2SLASH)
	if path[-1]=='/':
		return path[:-1]
	return path

def winpath(path):
		return string.translate(path, SLASH2BACKSLASH)

if os.name=='posix':
	SYSTEM = UNIX
	NULLDEV = '/dev/null'
	_normpath = unixpath
	SHELL = ''
else:
	SYSTEM = WINDOWS
	NULLDEV = 'nul'
	_normpath = unixpath
	SHELL = 'sh'

# --- globals  ----------------------------------------------------------

tool = None

# --- output to sniff
err = ''
out = ''

# --- input from sniff
repositoryFile=  ''
branchName=      ''		
branchFlag=      FALSE	
comment=	 ''
date=            ''	
workingFile=	 ''	     
catDir=	         ''	 
SSWE=		 ''    
SSWS=            ''	     
SOWS=	         ''	     
description=     '' 
pid=	         ''	     
lockType=	 ''	     
projectName=	 ''		
repositoryDir=	 ''	
RCCSPath=        ''	
repositoryRoot=  ''	
projectDir=	 ''	
symbolName=	 ''	
forceFlag=	 FALSE
tmpFile=	 ''
tmpDir=          ''
user=	         ''     
revision=        ''     
revision2=       ''	
workingDir=	 ''
privateRoot=     ''
sniffDir=	 ''
OSTempDir=	 ''


# --- utilities ------------------------------------------------------

def clr_output():
	global out, err
	out = ''   # global output string
	err = ''   # global error string

def output(*obj):
	global out
	out = out + string.join(obj, '')

def error(*obj):
	global err
	err = err + string.join(obj, '')

def test(rc):
	if rc:
		return FALSE
	else:
		return TRUE


class StringStream:
	def __init__(self, str):
		self.str = str
		self.idx = 0
		self.len = len(str)
		self.closed = FALSE
	def readline(self):
		if self.idx >= self.len:
			return ''
		i = string.find(self.str, NL, self.idx)
		if i < 0:
			i = self.len
		else:
			i = i+1
		line = self.str[self.idx:i]
		self.idx = i
		return line
	def close(self):
		if not self.closed:
			del self.str
			self.closed = TRUE
		return None

# Python has a very efficient StringIO module written in C
# let's try to import it..
try:
	from cStringIO import StringIO
	# use the efficient StringIO as our StringStream
	StringStream=StringIO
except ImportError:
	# bad luck :-(
	pass

def joinpath(*p):
	p = filter(lambda x: x, p)
	p = normpath(string.join(p, '/'))
	return p

def normpath(p):
	p = os.path.expandvars(p)
	p = os.path.normpath(p)
	p = _normpath(p)
	p = string.join(string.split(p,'$'),'\\$')
	return p

# UserName
# returns vcs.user with blanks etc replaced by underscore
BLANK2UNDERSCORE = string.maketrans(' ','_')
def UserName():
	return string.translate(user, BLANK2UNDERSCORE)

# --- system dependent utilities --------------------------------------

if SYSTEM==WINDOWS:
	def escape_quotes(str):
		str = string.join(string.split(str,'\\'), '\\\\')
		str = string.join(string.split(str, '\'"\'"\''), "\\'")
		str = string.join(string.split(str,'"'), '\\"')
		str = string.join(string.split(str,'`'), '\\`')
		str = string.join(string.split(str,'$'), '\\$')
		return str
else:
	def escape_quotes(str):
		str = string.join(string.split(str,'\\'), '\\\\')
		str = string.join(string.split(str, '\'"\'"\''), "'")
		str = string.join(string.split(str, '"'), '\\"')
		str = string.join(string.split(str,'`'), '\\`')
		str = string.join(string.split(str,'$'), '\\$')
		return str

def popen(cmd, file=''):
	if file:
		file = string.join(map(normpath, string.split(file)))
	if SYSTEM == UNIX:
		cmd = '%s %s' % (cmd, file)
	else:
		cmd = '%s -c \'%s %s\'' % (SHELL, cmd, file)
	if TEST: print "Popen:",cmd
	rc, _out = syscmd(cmd)
	return StringStream(_out)

def getoutput(cmd):
	global err
	if SYSTEM==WINDOWS:
		cmd = '%s -c \'%s\'' % (SHELL, cmd)
	if TEST: print "Getoutput:", cmd
	rc, _out = syscmd(cmd)
	return _out

def system(cmd):
	global err
	if SYSTEM==WINDOWS:
		cmd = '%s -c \'%s\'' % (SHELL, cmd)
	if TEST: print "System:", cmd
	rc, _out = syscmd(cmd)
	return rc

if SYSTEM==UNIX:
	sysexec = system
else:
	def sysexec(cmd):
		if TEST: print "Sysexec:", cmd
		rc, _out = syscmd(cmd)
		return rc

def syscmd(cmd):
	global err
	try:
		rc, _out, _err = RunCmd(cmd)
	except RuntimeError, arg:
		rc, _out, _err = -1, '', 'Error during fork: '+arg
	if rc:
		error(_err)
	if TEST:
		print _out[:2000]
		print _err, '(%d)' % rc
	return rc, _out

sh = system

def is_readable(path):
	st = get_stat(path)
	return test_readable(st)

def is_writable(path):
	st = get_stat(path)
	return test_writable(st)

if SYSTEM==UNIX:
	def is_executable(path):
		st = get_stat(path)
		return test_executable(st)
else:
	def is_executable(path):
		return os.path.exists(path+'.exe')


def is_reachable(cmd):
	for path in PATH_LIST:
		cmd_path = joinpath(path, cmd)
		if is_executable(cmd_path):
			return TRUE
	return FALSE
#or:
#    rc, _out, _err = RunCmd(cmd)
#    return test(rc)

def chmod(path, mode):
	try:
		_chmod(path, mode)
	except os.error, exc:
		global err
		err = err + ('chmod %s: os.error ' % path) + str(exc) + NL
		return 0
	return 0

def mkdir_p(path, mode=None):
	path = normpath(path)
	return _mkdir_p(path, mode)
def _mkdir_p(path, mode):
	if os.path.exists(path):
		return 0
	parent = os.path.dirname(path)
	_mkdir_p(parent, mode)
	try:
		os.mkdir(path)
	except os.error, exc:
		global err
		err = err + 'mkdir_p: os.error ' + str(exc) + NL
		return 1
	if mode:
		return chmod(path, mode)
	return 0

def rm_fr(path):
	return system('rm -fr %s' % normpath(path))

"""
def rm_f(path):
		return system('rm -f %s' % normpath(path))
"""

def rm_f(path):
	if SYSTEM==WINDOWS:
		chmod(path, 'ug+w')
	try:
		os.remove(path)
	except os.error, exc:
		global err
		err = 'remove: os.error ' + str(exc) + NL
		return 1
	except:
		return 1
	return 0

def touch(path):
	"""Change modification time of path to current time.
	If the file does not exist it is *not* created."""
	st = get_stat(path)
	if not st:
		return 1
	atime = get_atime(st)
	mtime = int(time.time())
	chgmode = FALSE
	if SYSTEM==WINDOWS and not test_writable(st):
		chgmode = TRUE
		mode = get_mode(st)
		chmod(path,'u+w')
	rc = 0
	try:
		os.utime(path,(atime,mtime))
	except os.error, exc:
		global err
		err = 'touch: os.error ' + str(exc) + NL
		rc = 1
	if chgmode:
		chmod(path,mode)
	return rc

def cp(src, dest):
	return system('cp %s %s' % (normpath(src), normpath(dest)))

def mv(src, dest):
	return system('mv %s %s' % (normpath(src), normpath(dest)))


def get_stat(file):	
	try:
		st = os.stat(file)
	except os.error:
		st = FALSE
	return st
def test_exists(st):
	return st and TRUE
def test_writable(st):
	return st and (st[ST_MODE] & 0200)
def test_readable(st):
	return st and (st[ST_MODE] & 0400)
def test_executable(st):
	return st and (st[ST_MODE] & 0100)
def test_mode(st, mask):
	return st and (st[ST_MODE] & mask)
def test_symlink(st):
	return S_ISLNK(st[ST_MODE])
def get_inode(st):
	return st[ST_INO]
def get_device(st):
	return st[ST_DEV]
def get_mtime(st):
	return st[ST_MTIME]
def get_atime(st):
	return st[ST_ATIME]
def get_mode(st):
	return st[ST_MODE]
def samefile(st1, st2):
	if SYSTEM==WINDOWS:
		raise RuntimeError, 'vcs.samefile does not work on Windows'
	return get_inode(st1)==get_inode(st2) and get_device(st1)==get_device(st2)


# --- interface ------------------------------------------------------

# SetTool:
# Set a tool by name
# see global mapping TOOLS for defined names

def SetTool(tool_name):
	global tool, err
	clr_output()
	if TEST: print 'SetTool: %s' % tool_name
	try:
		tool_module = TOOLS[tool_name]
	except KeyError:
		tool_module = find_tool(tool_name)
	try:
		tool = __import__(tool_module)
	except ImportError:
		tool = None
	if not tool:
		err = 'Tool %s not found.' % tool_name
		return FALSE
	if TEST: print 'Tool module:', tool.__file__
	return TRUE

def find_tool(name):
	for tool_module in TOOLS.values():
		if string.find(name, tool_module) >= 0:
			return tool_module
	return name

# ---- VCS commands ----------------------------------

def CheckOut():
	if TEST: print "CheckOut: "
	clr_output()
	return tool.CheckOut()

def CheckOutProject():
	if TEST: print "CheckOutProject: "
	clr_output()
	return tool.CheckOutProject()

def CreateRepositoryFile():
	if TEST: print "CreateRepositoryFile: "
	clr_output()
	return tool.CreateRepositoryFile()

def CheckIn():
	if TEST: print "CheckIn: "
	clr_output()
	return tool.CheckIn()

def Lock():
	if TEST: print "Lock: "
	clr_output()
	return tool.Lock()

def SetWorkFileReadOnly():
	if TEST: print "SetWorkFileReadOnly: "
	clr_output()
	return tool.SetWorkFileReadOnly()

def UpdateProject():
	if TEST: print "UpdateProject: "
	clr_output()
	return tool.UpdateProject()

def UpdateFile():
	if TEST: print "UpdateFile: "
	clr_output()
	return tool.UpdateFile()

def Unlock():
	if TEST: print "Unlock: "
	clr_output()
	return tool.Unlock()

def BreakLock():
	if TEST: print "BreakLock: "
	clr_output()
	return tool.BreakLock()

def DeleteVersion():
	if TEST: print "DeleteVersion: "
	clr_output()
	return tool.DeleteVersion()

def SetDescription():
	if TEST: print "SetDescription: "
	clr_output()
	return tool.SetDescription()

def SetSymbol():
	if TEST: print "SetSymbol: "
	clr_output()
	return tool.SetSymbol()

def DeleteSymbol():
	if TEST: print "DeleteSymbol: "
	clr_output()
	return tool.DeleteSymbol()

def ChangeComment():
	if TEST: print "ChangeComment: "
	clr_output()
	return tool.ChangeComment()

def Description():
	if TEST: print "Description: "
	clr_output()
	return tool.Description()

def History():
	if TEST: print "History: "
	clr_output()
	return tool.History()

def Head():
	if TEST: print "Head: "
	clr_output()
	return tool.Head()

def SymbolList():
	if TEST: print 'SymbolList:'
	clr_output()
	return tool.SymbolList()

def RevisionOfSymbol():
	if TEST: print "RevisionOfSymbol: "
	clr_output()
	return tool.RevisionOfSymbol()

def HistoryOfRevision():
	if TEST: print "HistoryOfRevision: "
	clr_output()
	return tool.HistoryOfRevision()

def Comment():
	if TEST: print "Comment: "
	clr_output()
	return tool.Comment()

def Lockers():
	if TEST: print 'Lockers:'
	clr_output()
	return tool.Lockers()

def IsLockedBy():
	if TEST: print "IsLockedBy: "
	clr_output()
	return tool.IsLockedBy()

def IsLocked():
	if TEST: print "IsLocked: "
	clr_output()
	return tool.IsLocked()

def RepositoryDir():
	clr_output()
	return tool.RepositoryDir()

def RepositoryFile():
	clr_output()
	return tool.RepositoryFile()

def TempDir():
	clr_output()
	return tool.TempDir()

def TempFile():
	clr_output()
	return tool.TempFile()

def BranchDef():
	clr_output()
	return tool.BranchDef()

def RepositoryCheckDef():
	clr_output()
	return tool.RepositoryCheckDef()

def IsDifferent():
	if TEST: print "IsDifferent: "
	clr_output()
	return tool.IsDifferent()

def DiffText_W_V():
	if TEST: print "DiffText_W_V: "
	clr_output()
	return tool.DiffText_W_V()

def DiffText_V_V():
	if TEST: print "DiffText_V_V: "
	clr_output()
	return tool.DiffText_V_V()

def CheckRepositoryDir():
	if TEST: print "CheckRepositoryDir: "
	clr_output()
	return tool.CheckRepositoryDir()

def CheckRepositoryFile():
	if TEST: print "CheckRepositoryFile: "
	clr_output()
	return tool.CheckRepositoryFile()

def DateOfRepositoryFile():
	if TEST: print 'DateOfRepositoryFile:'
	clr_output()
	return tool.DateOfRepositoryFile()

def CreateRepositoryDir():
	if TEST: print 'CreateRepositoryDir:'
	clr_output()
	return tool.CreateRepositoryDir()

def TestTool():
	if TEST: print 'TestTool:'
	clr_output()
	return tool.TestTool()

def Constructor():
	if TEST: print 'Constructor:'
	clr_output()
	return tool.Constructor()

def Destructor():
	if TEST: print 'Destructor:'
	clr_output()
	return tool.Destructor()


# --- debug stuff -------------------------------------------------

try:
	TEST = os.environ['VCSDEBUG']
except KeyError:
	TEST = ''

if TEST=='tmp':
	tf = open('/tmp/vcs_trace', 'w', 0)
	stdout = sys.stdout
	stderr = sys.stderr
	sys.stdout = tf
	sys.stderr = tf
	def close_trace():
		tf.close()
		sys.stdout = stdout
		sys.stderr = stderr
	sys.exitfunc = close_trace
if TEST: print 'VCS module:',__file__

try:
	TEST1 = os.environ['VCSDEBUG1']
except KeyError:
	TEST1 = None
if TEST1 is not None:
	class CmdWrapper:
		def __init__(self,cmd):
			self.cmd=cmd
		def __call__(self):
			try:
				self.PrintArgs()
				result=self.cmd()
			except:
				result=None
				import traceback
				traceback.print_exc()
			return result
		def PrintArgs(self):
			if not TEST1:
				return
			args=[]
			for c,name,comment in _variables:
				try:
					value=_module_dict[name]
				except KeyError:
					value=None
				if value:
					args.append('%s=%s'% (name,repr(value)))
			print self.cmd.__name__,tuple(args)
	_module_dict=globals()
	for cmdname in _commands:
		cmd=_module_dict[cmdname]
		_module_dict[cmdname]=CmdWrapper(cmd)
